package client;

import interfaces.*;
import client.*;
import serveur.*;
import java.io.*;
import java.rmi.*;
import java.util.*;
import java.rmi.server.UnicastRemoteObject;
import java.lang.management.*;

// TODO : threads : foutre un putain d'extends Thread implements Serializable
// dans le token

public class ClientImpl extends UnicastRemoteObject implements ClientITF {

	Vector neighbourhood = new Vector();
	int id;
	int capacity;
	
	Vector inbox = new Vector();
	Vector sendbox = new Vector();

	ServeurITF serveur;

	public ClientImpl() throws RemoteException
	{
		id = -1;
		capacity = getCapacity();
	}
	
	/*
	   Méthodes de set/get classiques.
	*/
	
	public int getID() throws RemoteException
	{
		return id;
	}
	
	public void setID(int _id) throws RemoteException
	{
		id = _id;
	}
	
	public void setNeighbourhood(Vector _neighbourhood) throws RemoteException
	{
		neighbourhood.clear();
		for (ClientITF c : (Vector<ClientITF>) _neighbourhood)
		{
			neighbourhood.add(c);
		}
	}
	
	public Vector getNeighbourhood() throws RemoteException
	{
		return neighbourhood;
	}
	
	public int getCapacity() throws RemoteException
	{
		int capacity = (int) (ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100);
		return capacity;
	}
	
	public void setServer(ServeurITF _serveur) throws RemoteException
	{
		serveur = _serveur;
	}
	
	public ServeurITF getServer() throws RemoteException
	{
		return serveur;
	}
	
	public void addMail(MessageITF msg) throws RemoteException
	{
		inbox.add(msg);
	}
	
	public void removeMail(MessageITF msg) throws RemoteException
	{
		inbox.remove(msg);
	}
	
	public boolean equals(ClientITF client) throws RemoteException
	{
		return id == client.getID();
	}

	/*
	   Méthodes d'ajout/suppression d'un client à la liste de voisins.
	*/
	
	public void addNeighbour(ClientITF client) throws RemoteException
	{
		System.out.println("* Ajout d'un voisin ("+ client.getID() +")");
		neighbourhood.add(client);
		System.out.println("    Voisins: " + printNeighbours());
	} 
	
	public void removeNeighbour(ClientITF client) throws RemoteException
	{
		System.out.println("* Suppression d'un voisin ("+ client.getID() +")");
		neighbourhood.remove(client);
		System.out.println("    Voisins: " + printNeighbours());
	}

	/*
		Méthodes de connexion/déconnexion au serveur en dur si nécessaire.
	*/
	
	public ServeurITF connect_to(String host) throws RemoteException
	{
		ServeurITF server = null;
		
		// On retrouve le server		
		try
		{
			System.out.println("* Tentative de connexion au serveur " + host);
			server = (ServeurITF) Naming.lookup("rmi://" + host + "/Serveur");
		}
		catch (Exception e)
		{
			System.out.println(e);
			System.exit(0);
		}
		
		serveur = server;
		
		// On se connecte au server
		serveur.connect(this);
		
		return server;
	}
	
	public void disconnect() throws RemoteException
	{
		serveur.disconnect(this);
	}

	public int getNbMachines(ServeurITF serveur) throws RemoteException
	{
		return serveur.nbClients();
	}

	/*
		Méthodes sur les messages, ils seront rentrés au clavier
		ou entrés en dur.
	*/
	
	public MessageITF writeMail() throws RemoteException
	{
		BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
		String str = "";
		
		System.out.println("Message : ");
		
		try
		{
			str = keyboard.readLine();
		}
		catch (IOException e)
		{
			System.out.println(e);
		}
		
		MessageImpl msg = new MessageImpl(this, str);		
		
		return msg;
	}
	
	// Envoi du message à tous les voisins
	public void sendMail() throws RemoteException
	{
		MessageITF msg = writeMail();
		msg.addHaveSeen(this);

		for (ClientITF n : (Vector<ClientITF>) neighbourhood)
		{
			n.addMail(msg);
		}
	}
	
	// Envoi du message à tout le graphe
	public void forwardMail() throws RemoteException
	{
		if (checkMails())
		{
			for (MessageITF m : (Vector<MessageITF>) inbox)
			{
				for (ClientITF n : (Vector<ClientITF>) neighbourhood)
				{
					if (n.getID() != m.getIdClient())
					{
						m.addHaveSeen(this);
						n.addMail(m);
					}
				}
			}
			
			inbox.clear();
		}
	}
	
	public void checkBox() throws RemoteException
	{
		long timer;
		boolean time_flag;
		
		// Vérification régulière de l'inbox
		
		while (true)
		{
			timer = System.currentTimeMillis();
			time_flag = false;
		
			while (!time_flag)
			{
				time_flag = (System.currentTimeMillis() - timer > 400);
				try { Thread.sleep(200); } catch (Exception e) {}
			}
			
			checkMails();
		}			
	}
	
	public boolean checkMails() throws RemoteException
	{
		boolean flag = !inbox.isEmpty();
		
		if (flag)
		{
			for (MessageITF m : (Vector<MessageITF>) inbox)
			{
				System.out.println("Message : " + m.getText());
			}
		
			inbox.clear();
		}
		
		return flag;
	}
	
	public void answerMail(MessageITF msg) throws RemoteException
	{
		MessageITF answer = writeMail();
		ClientITF  asker  = (ClientITF) neighbourhood.elementAt(msg.getIdClient());
		asker.addMail(answer);
	}

	public String printNeighbours() throws RemoteException
	{
		String r = "";
		
		if (getNbNeighbours() == 0)
		{
			r = "[]";
		}
		else
		{
			for (ClientITF c : (Vector<ClientITF>) neighbourhood)
			{
				r += "["  + c.getID() + "] ";
			}
		}
		
		return r;
	}
	
	public String printGraph() throws RemoteException
	{
		return serveur.printGraph();
	}
	
	public int getNbNeighbours() throws RemoteException
	{
		return neighbourhood.size();
	}
	
	public void lancerElection() throws RemoteException
	{
		// Init		
		TokenITF jeton = new TokenImpl(this);

		// On passe le jeton à n'importe quel voisin (le premier)
		next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
		
	}

	public void transfertJeton(TokenITF jeton, ClientITF from) throws RemoteException
	{
		System.out.println("Jeton reçu ...");
		
		// Si le message à déjà été vu
		if (jeton.hadBeenSeen(this))
		{
			// et qu'on est pas le demandeur initial
			if (getID() != jeton.getSender())
			{
				// On passe le jeton à un autre
				next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
			}
			else // sinon on a fini notre demande
			{
				// TODO : FIN R(c_i, i) au Demandeur
				
				System.out.println("Fin d'élection - l'élu est " +
				                   jeton.getIdMax() + " (" +
				                   jeton.getCapacityMax() + ")");

				// TODO : renvoi de la machine choisie au serveur 
				serveur.resultatElection(jeton.getIdMax(), this);

				return;
			}
			
		}
		else
		{
			// On regarde si on a pas une meilleure capacité
			// !!!! ATTENTION : meilleure capacité signifie "inférieure" en fait
			
			int capacity = getCapacity();
			System.out.println("moi : " + capacity + ", jeton : " + jeton.getCapacityMax());
			
			if (capacity <= jeton.getCapacityMax())
			{	
				jeton.setIdMax(this.getID());
				jeton.setCapacityMax(capacity);
			}
			jeton.addHaveSeen(this);
			
			// On passe le jeton à un autre
			next(jeton, getNeighbourhood()).transfertJeton(jeton, this);
		}
		
		System.out.println("Jeton renvoyé !");
	}
	
	public ClientITF next(TokenITF jeton, Vector<ClientITF> neighbours) throws RemoteException
	{
		// Méthode qui va chercher le prochain à qui envoyer le jeton
		ClientITF send_to;
		int index;
		
		// On cherche une personne dans ses voisins qui n'a pas eu le jeton		
		for (ClientITF n : (Vector<ClientITF>) neighbours)
		{
			if (!jeton.hadBeenSeen(n))
			{
				return n;
			}
		}
		
		// Si tout le monde l'a vu, on l'envoye à la personne
		// qui l'a reçu avant nous dans le addSeen
		index = jeton.getHaveSeen().indexOf(this);
		
		if (index > 0)
		{
			return (ClientITF) jeton.getHaveSeen().elementAt(index-1);
		}
		
		// Si on était en premier, on est le sender, ce qui revient à :		
		return jeton.getSenderITF();
	}

	public void choisiPar(ClientITF sender) throws RemoteException
	{
		// TODO : utiliser le streaming
		System.out.println("J'ai été élu par " + sender.getID() + " !!");
	}
}
 
